package frontend.lexer;

import exceptions.LexerInnerException;
import frontend.cehandler.compilationerrors.CEType;
import frontend.cehandler.CEHandler;
import frontend.lexer.token.StrConToken;
import frontend.lexer.token.Token;
import frontend.lexer.token.TokenType;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;

public class Lexer {
    final Scanner scanner;
    final PrintWriter printWriter;
    final PrintWriter debugWriter;
    final CEHandler ceHandler;
    final ArrayList<String> sourceCodeList;
    private final LexerSupporter supporter;
    private final ArrayList<Token> tokens = new ArrayList<>();

    public Lexer(Scanner scanner, ArrayList<String> sourceCodeList, PrintWriter printWriter, PrintWriter debugWriter, CEHandler ceHandler) {
        this.scanner = scanner;
        this.sourceCodeList = sourceCodeList;
        this.printWriter = printWriter;
        this.debugWriter = debugWriter;
        this.ceHandler = ceHandler;
        this.supporter = new LexerSupporter(this);
    }
    public void analyseAll() {
        while(analyseOne() != null) {}
    }

    public ArrayList<Token> getTokens() {
        return tokens;
    }
    public ArrayList<String> getSourceCodeList() {
        return supporter.getSourceCodeList();
    }


    /**
     * 从当前位置略过一次注释
     * 前置条件：pos指向了第一个'/'。
     * 后置条件：pos指向略过注释后的第一个位置。
     * @return 是否略过了注释
     */
    private boolean handleAnnotation() {
        if (!(supporter.now() == '/')) throw new LexerInnerException();
        if (!supporter.hasNext()) return false;
        int annotationStatus = 1;
        switch (supporter.next()) {
            case '/' -> {
                annotationStatus = 2;
                supporter.push(2);
            }
            case '*' -> {
                annotationStatus = 5;
                supporter.push(2);
            }
            default -> {
                return false;
            }
        }

        for (; supporter.hasNow(); supporter.push()) {
            switch (annotationStatus) {
                case 2 -> {
                    if (supporter.now() == '\n') {
                        supporter.anotherLine();
                        supporter.push();
                        return true;
                    }
                }
                case 5 -> {
                    if (supporter.now() == '*') {
                        annotationStatus = 6;
                    }
                    else if (supporter.now() == '\n') {
                        supporter.anotherLine();
                    }
                }
                case 6 -> {
                    if (supporter.now() == '/') {
                        supporter.push();
                        return true;
                    }
                    else if (supporter.now() == '*') {
                        continue;
                    }
                    else {
                        annotationStatus = 5;
                        if (supporter.now() == '\n') {
                            supporter.anotherLine();
                        }
                    }
                }
            }
        }

        if (annotationStatus == 2) {
            return true;
        } else {
            //error
            ceHandler.lexerError(CEType.ol,
                    "annotation reach the end of source code but not ended with '*/'",
                    supporter.lineNum() - 1,
                    supporter.lineNum() - 1,
                    1, getSourceCodeList().get(supporter.lineNum() - 2).length() + 1);
            return true;
        }
    }

    private void handleBlank() {
        while (supporter.hasNow()) {
            switch (supporter.now()) {
                case ' ', '\t', '\r', '\f', '\u000B' -> {
                    supporter.push();
                }
                case '\n' -> {
                    supporter.anotherLine();
                    supporter.push();
                }
                case '/' -> {
                    if (!handleAnnotation()) {
                        return;
                    }
                }
                default -> {
                    return;
                }
            }
        }
    }
    private boolean handleReservedWordCondition() {
        return Character.isLetter(supporter.now()) || supporter.now() == '_';
    }

    private Token handleReservedWord() {
        if (!handleReservedWordCondition()) throw new LexerInnerException();
        int lineNum = supporter.lineNum(), linePosStart = supporter.linePos();
        StringBuilder sb = new StringBuilder();
        for (; supporter.hasNow() && (Character.isLetter(supporter.now()) || Character.isDigit(supporter.now()) || supporter.now() == '_'); supporter.push()) {
            sb.append(supporter.now());
        }
        int linePosEnd = supporter.linePos();
        String s = sb.toString();
        return supporter.makeToken(TokenType.reservedWord.getOrDefault(s, TokenType.IDENFR), lineNum, linePosStart, linePosEnd);
    }
    private boolean handleIntConstCondition() {
        return Character.isDigit(supporter.now());
    }
    private Token handleIntConst() {
        if (!handleIntConstCondition()) throw new LexerInnerException();
        int lineNum = supporter.lineNum(), linePosStart = supporter.linePos();
        boolean raiseError = supporter.now() == '0' && supporter.hasNext() && Character.isDigit(supporter.next());

        int intVal = 0;
        for (; supporter.hasNow() && Character.isDigit(supporter.now()); supporter.push()) {
            intVal *= 10;
            intVal += supporter.now() - '0';
        }
        int linePosEnd = supporter.linePos();
        Token ret = supporter.makeToken(TokenType.INTCON, intVal, lineNum, linePosStart, linePosEnd);

        if (raiseError) {
            ceHandler.lexerError(CEType.ol, "IntConst begin with 0", ret);
        }
        return ret;
    }
    private boolean handleStrConstCondition() {
        return supporter.now() == '\"';
    }
    private Token handleStrConst() {
        if (!handleStrConstCondition()) throw new LexerInnerException();

        // 获取lineNum、linePosStart、linePosEnd、stringVal
        int lineNum = supporter.lineNum(), linePosStart = supporter.linePos();
        StringBuilder sb = new StringBuilder();
        supporter.push();
        for (; supporter.hasNow() && supporter.now() != '\"'; supporter.push()) {
            sb.append(supporter.now());
            if (supporter.now() == '\n') {
                supporter.anotherLine();
            }
        }
        boolean raiseError = !supporter.hasNow();
        supporter.push();
        String stringVal = sb.toString();
        // TODO: 以后修改简化linePosEnd的逻辑
        int linePosEnd = supporter.lineNum() == lineNum ?
                supporter.linePos() :
                getSourceCodeList().get(lineNum - 1).length() + 1;


        // 遍历stringVal，构建StrConItems并检查非法字符
        ArrayList<StrConToken.StrConItem> strConItems = new ArrayList<>();
        StringBuilder curStrConItemContent = new StringBuilder();
        StringBuilder illegalCharsMessage = new StringBuilder("StrConst contains illegal character(s):");
        Set<Character> illegalChars = new HashSet<>();
        for (int i = 0; i < stringVal.length(); i++) {
            char c = stringVal.charAt(i);
            if (!((int)c >= 40 && (int)c <= 126 || (int)c == 32 || (int)c == 33 || (int)c == 37)) {
                illegalChars.add(c);
            }
            else if (c == '\\') {
                if (i + 1 >= stringVal.length() || stringVal.charAt(i + 1) != 'n') {
                    illegalChars.add(c);
                }
                else {
                    curStrConItemContent.append('\n');
                    i++;
                }
            }
            else if (c == '%') {
                if (i + 1 >= stringVal.length() || stringVal.charAt(i + 1) != 'd') {
                    illegalChars.add(c);
                } else {
                    strConItems.add(buildStrConItem(curStrConItemContent.toString()));
                    strConItems.add(new StrConToken.PlaceHolderItem("%d"));
                    curStrConItemContent = new StringBuilder();
                    i++;
                }
            }
            else {
                curStrConItemContent.append(c);
            }
        }
        if (!curStrConItemContent.isEmpty()) {
            strConItems.add(buildStrConItem(curStrConItemContent.toString()));
        }

        // 构建Token
        Token ret = supporter.makeToken(
                TokenType.STRCON,
                stringVal,
                strConItems,
                lineNum,
                linePosStart,
                linePosEnd
        );

        // 报错
        // 非法字符报错
        if (!illegalChars.isEmpty()) {
            for (Character c: illegalChars) {
                illegalCharsMessage.append('\'');
                illegalCharsMessage.append(c);
                illegalCharsMessage.append('\'');
                if (c == '\\') {
                    illegalCharsMessage.append("(but not '\\n')");
                } else if (c == '%') {
                    illegalCharsMessage.append("(but not '%d')");
                }
                illegalCharsMessage.append(',');
            }
            illegalCharsMessage.deleteCharAt(illegalCharsMessage.length() - 1);
            ceHandler.lexerError(CEType.a, illegalCharsMessage.toString(), ret);
        }

        // 读到文件末尾报错
        if (raiseError) {
            ceHandler.lexerError(CEType.ol, "StrConst not ended with '\"'", ret);
        }

        return ret;
    }

    private StrConToken.StrConItem buildStrConItem(String strConItemContent) {
        if (strConItemContent.length() == 1) {
            return new StrConToken.CharItem(strConItemContent.charAt(0));
        }
        else {
            return new StrConToken.StrItem(strConItemContent);
        }
    }

    private Token handleDelimiter(int delimiterMaxLength) {
        int lineNum = supporter.lineNum(), linePosStart = supporter.linePos();
        StringBuilder sb = new StringBuilder();
        int length;
        for (length = 0; length < delimiterMaxLength && supporter.hasNext(length); length++) {
            sb.append(supporter.next(length));
        }
        String s;
        for (; length > 0; length--) {
            s = sb.substring(0, length);
            if (TokenType.delimiter.containsKey(s)) {
                supporter.push(length);
                int linePosEnd = supporter.linePos();
                return supporter.makeToken(TokenType.delimiter.get(s), lineNum, linePosStart, linePosEnd);
            }
        }
        return null;
    }

    public Token analyseOne() {
        while (true) {
            // 略过空格和注释
            handleBlank();

            // 是否读到结尾
            if (!supporter.hasNow()) {
                return null;
            }

            Token ret = null;

            // 标识符或关键字
            if (handleReservedWordCondition()) {
                ret = handleReservedWord();
            }

            // 数字常量
            else if (handleIntConstCondition()) {
                ret = handleIntConst();
            }

            // String常量
            else if (handleStrConstCondition()) {
                ret = handleStrConst();
            }

            // 分隔符
            else if ( (ret = handleDelimiter(2)) != null) {}

            // 无法识别
            else {
                ceHandler.lexerError(CEType.ol,
                        "Could not indent the syntax:'" + supporter.now() + '\'',
                        supporter.lineNum(), supporter.lineNum(),
                        supporter.linePos(), supporter.linePos() + 1);
                supporter.push();
                continue;
            }

            // 发生其他错误
            if (ret == null) {
                continue;
            }

            tokens.add(ret);

            printWriter.println(ret.toString());

            return ret;
        }

    }
}
